﻿//-----------------------------------------------
// RedditSharp (Reddit#)
//
// Copyright (c) 2011 - 2012
// - Angelo Geels <angelo@angelog.nl>
//-----------------------------------------------

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;

namespace RedditSharp
{
    public delegate void RedditErrorHandler(string Reason);

    public class Reddit
    {
        /// <summary>
        /// The modhash that's sent to reddit.
        /// </summary>
        public static string ModHash = "";

        /// <summary>
        /// The cookie that's sent to reddit.
        /// </summary>
        public static string Cookie = "";

        /// <summary>
        /// Error handler.
        /// </summary>
        public static event RedditErrorHandler Error;

        private static RedditUser _Me;
        /// <summary>
        /// Gets the currently logged in user. Cached in memory.
        /// </summary>
        public static RedditUser Me
        {
            get
            {
                if (_Me == null && Cookie != "" && ModHash != "")
                    ReloadMe();

                return _Me;
            }
        }

        /// <summary>
        /// Log in to reddit using a cookie file.
        /// </summary>
        /// <param name="CookieFile">The filename of the cookie contents</param>
        public static void Login(string CookieFile)
        {
            if (!File.Exists(CookieFile))
                return;

            string[] session = File.ReadAllText(CookieFile).Split('\n');

            ModHash = session[0];
            Cookie = session[1];
        }

        /// <summary>
        /// Log in to reddit using a username and a password.
        /// </summary>
        /// <param name="Username">The username</param>
        /// <param name="Password">The password</param>
        public static void Login(string Username, string Password)
        {
            string EscapedUsername = Uri.EscapeDataString(Username);
            string EscapedPassword = Uri.EscapeDataString(Password);

            string Data = "api_type=json&user=" + EscapedUsername + "&passwd=" + EscapedPassword;
            string Result = Upload("api/login/" + EscapedUsername, Data);

            Hashtable loginResult = JSON.JsonDecode(Result).ToHashtable();
            Hashtable loginData = loginResult["json"].ToHashtable()["data"].ToHashtable();

            string redditModhash = (string)loginData["modhash"];
            string redditCookie = (string)loginData["cookie"];

            ModHash = "uh=" + Uri.EscapeDataString(redditModhash);
            Cookie = "reddit_session=" + Uri.EscapeDataString(redditCookie);
        }

        /// <summary>
        /// Log the currently logged in user out.
        /// </summary>
        public static void Logout()
        {
            ModHash = "";
            Cookie = "";
            _Me = null;
        }

        /// <summary>
        /// Forces a recache on the Me property.
        /// </summary>
        public static void RecacheMe()
        {
            _Me = null;
        }

        /// <summary>
        /// Reloads the cache on the Me property.
        /// </summary>
        public static void ReloadMe()
        {
            _Me = GetUser();
        }

        /// <summary>
        /// Saves the reddit cookie and modhash into a file.
        /// </summary>
        /// <param name="CookieFile">The cookie filename</param>
        public static void SaveCookie(string CookieFile)
        {
            File.WriteAllText(CookieFile, ModHash + "\n" + Cookie);
        }

        /// <summary>
        /// Removes the reddit cookie and modhash.
        /// </summary>
        /// <param name="CookieFile">The cookie filename</param>
        public static void DeleteCookie(string CookieFile)
        {
            File.Delete(CookieFile);
        }

        /// <summary>
        /// Download a json page from Reddit. Automatically prepends www.reddit.com and postpends /.json.
        /// </summary>
        /// <param name="URL">The URL, such as r/pics or user/ansjh</param>
        /// <returns>Result in a json string</returns>
        internal static string Download(string URL)
        {
            return Download(URL, "");
        }

        /// <summary>
        /// Download a json page from Reddit. Automatically prepends www.reddit.com and postpends /.json.
        /// </summary>
        /// <param name="URL">The URL, such as r/pics or user/ansjh</param>
        /// <param name="Params">The URL parameters, such as ?count=25</param>
        /// <returns>Result in a json string</returns>
        internal static string Download(string URL, string Params)
        {
            WebClient wc = new WebClient();

            wc.Proxy = null;
            wc.Headers.Add(HttpRequestHeader.UserAgent, "RedditSharp/reddit.codeplex.com");
            wc.Headers.Add(HttpRequestHeader.Cookie, Cookie);

            string ret = wc.DownloadString("http://" + "www.reddit.com/" + URL + "/.json" + Params);
            
            // Lazy error checking
            int errorPos = ret.IndexOf("[\".error.");
            if (errorPos >= 0)
            {
                int endPosition = ret.IndexOf("\"", errorPos + 1);
                string errorMessage = ret.Substring(errorPos, endPosition - errorPos);

                if (Error != null)
                    Error(errorMessage);

                return "";
            }

            return ret;
        }

        /// <summary>
        /// Upload a POST request to reddit.
        /// </summary>
        /// <param name="URL">The URL, such as api/login/ansjh or api/vote</param>
        /// <param name="Data">The data sent, such as api_type=json&user=angelerp&passwd=hunter2</param>
        /// <returns>Result in a json string</returns>
        internal static string Upload(string URL, string Data)
        {
            WebClient wc = new WebClient();

            wc.Proxy = null;
            wc.Headers.Add(HttpRequestHeader.UserAgent, "RedditSharp/reddit.codeplex.com");
            wc.Headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded");
            wc.Headers.Add(HttpRequestHeader.Cookie, Cookie);

            return wc.UploadString("http://" + "www.reddit.com/" + URL, (ModHash != "" ? ModHash + "&" : "") + Data);
        }

        /// <summary>
        /// Returns top posts on the frontpage.
        /// </summary>
        /// <returns>Array of the reddit posts</returns>
        public static RedditPost[] GetPosts()
        {
            return GetPosts(false);
        }

        /// <summary>
        /// Returns top posts on the frontpage, from the given page.
        /// </summary>
        /// <param name="Page">The page to get the posts from, starting at 1</param>
        /// <returns>Array of the reddit posts</returns>
        public static RedditPost[] GetPosts(int Page)
        {
            return GetPosts(false, Page);
        }

        /// <summary>
        /// Returns top posts on the frontpage.
        /// </summary>
        /// <param name="New">Wether to get the posts from /new or not</param>
        /// <returns>Array of the reddit posts</returns>
        public static RedditPost[] GetPosts(bool New)
        {
            return GetPosts(New, 1);
        }

        /// <summary>
        /// Returns top posts on the frontpage, from the given page.
        /// </summary>
        /// <param name="New">Wether to get the posts from /new or not</param>
        /// <param name="Page">The page to get the posts from, starting at 1</param>
        /// <returns>Array of the reddit posts</returns>
        public static RedditPost[] GetPosts(bool New, int Page)
        {
            return GetPostsInternal((New ? "/new" : ""), "?count=" + ((Page - 1) * 25));
        }

        /// <summary>
        /// Returns top posts in a subreddit.
        /// </summary>
        /// <param name="Subreddit">The subreddit to get the posts from</param>
        /// <returns>Array of the reddit posts</returns>
        public static RedditPost[] GetPosts(string Subreddit)
        {
            return GetPosts(Subreddit, false);
        }

        /// <summary>
        /// Returns top or new posts in a subreddit.
        /// </summary>
        /// <param name="Subreddit">The subreddit to get the posts from</param>
        /// <param name="New">Wether to get the posts from /new or not</param>
        /// <returns>Array of the reddit posts</returns>
        public static RedditPost[] GetPosts(string Subreddit, bool New)
        {
            return GetPosts(Subreddit, New, 1);
        }

        /// <summary>
        /// Returns top or new posts in a subreddit, with the given page.
        /// </summary>
        /// <param name="Subreddit">The subreddit to get the posts from</param>
        /// <param name="New">Wether to get the posts from /new or not</param>
        /// <param name="Page">The page to get the posts from, starting at 1</param>
        /// <returns>Array of the reddit posts</returns>
        public static RedditPost[] GetPosts(string Subreddit, bool New, int Page)
        {
            return GetPostsInternal("r/" + Subreddit + (New ? "/new" : ""), "?count=" + ((Page - 1) * 25));
        }

        /// <summary>
        /// Returns posts from a search result.
        /// </summary>
        /// <param name="Query">The search query</param>
        /// <returns>Array of the reddit posts</returns>
        public static RedditPost[] SearchPosts(string Query)
        {
            return GetPostsInternal("search", "?q=" + Uri.EscapeDataString(Query));
        }

        /// <summary>
        /// Returns posts from a specified URL.
        /// </summary>
        /// <param name="URL">The URL</param>
        /// <returns>Array of the reddit posts</returns>
        private static RedditPost[] GetPostsInternal(string URL)
        {
            return GetPostsInternal(URL, "");
        }

        /// <summary>
        /// Returns posts from a specified URL, allowing URL parameters.
        /// </summary>
        /// <param name="URL">The URL</param>
        /// <param name="Params">The URL parameters, such as ?count=25</param>
        /// <returns>Array of the reddit posts</returns>
        private static RedditPost[] GetPostsInternal(string URL, string Params)
        {
            List<RedditPost> ret = new List<RedditPost>();

            Hashtable response = JSON.JsonDecode(Download(URL, Params)).ToHashtable();

            ArrayList Posts = response["data"].ToHashtable()["children"].ToArrayList();
            foreach (Hashtable Post in Posts)
                ret.Add(RedditPost.Parse(Post["data"].ToHashtable()));

            return ret.ToArray();
        }

        /// <summary>
        /// Returns comments from a RedditPost.
        /// </summary>
        /// <param name="Post">The RedditPost</param>
        /// <returns>Array of the reddit comments</returns>
        public static RedditThread GetThread(RedditPost Post)
        {
            return GetThread(Post.ID);
        }

        /// <summary>
        /// Returns comments from a post ID.
        /// </summary>
        /// <param name="PostID">The post ID</param>
        /// <returns>Array of the reddit comments</returns>
        public static RedditThread GetThread(string PostID)
        {
            ArrayList response = JSON.JsonDecode(Download("comments/" + PostID)).ToArrayList();
            return RedditThread.Parse(response);
        }

        /// <summary>
        /// Returns the currently logged in user.
        /// </summary>
        /// <returns>The RedditUser</returns>
        public static RedditUser GetUser()
        {
            string result = Download("api/me");
            Hashtable data = JSON.JsonDecode(result).ToHashtable();
            return RedditUser.Parse(data["data"].ToHashtable());
        }

        /// <summary>
        /// Returns the user from a username.
        /// </summary>
        /// <param name="Username">The username</param>
        /// <returns>The RedditUser</returns>
        public static RedditUser GetUser(string Username)
        {
            string result = Download("user/" + Uri.EscapeDataString(Username) + "/about");
            Hashtable data = JSON.JsonDecode(result).ToHashtable();
            return RedditUser.Parse(data["data"].ToHashtable());
        }
    }
}
